package com.simple.core.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.TypeUtil;
import com.google.common.collect.Lists;
import com.simple.annotation.validate.Validate;
import com.simple.core.dao.BaseRepository;
import com.simple.core.dao.SimpleSliceJpaRepository;
import com.simple.core.dto.PageDto;
import com.simple.core.entity.BaseEntity;
import com.simple.core.service.IService;
import com.simple.core.spec.DeleteSpec;
import com.simple.core.validate.ValidGroup;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.util.Streamable;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.criteria.*;
import javax.persistence.metamodel.EntityType;
import javax.persistence.metamodel.Metamodel;
import javax.persistence.metamodel.SingularAttribute;
import java.io.Serializable;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;

/**
 * Service实现类
 *
 * @author FanXing
 * @date 2023年03月01日 19:55
 */
@Slf4j
@Transactional(rollbackFor = Exception.class)
public abstract class ServiceImpl<T, R extends BaseRepository> implements IService<T> {
    private static final int BATCH_SIZE = 1000;
    /**
     * 约定的删除字段
     */
    private static final String DELETED_FIELD = BaseEntity.Fields.deleted;
    private final Class<T> entityClazz = (Class<T>) TypeUtil.getTypeArgument(getClass(), 0);
    private static final Map<Class, SingularAttribute> ID_PROPERTY_CACHE = new ConcurrentHashMap<>();
    @Autowired
    protected R repository;
    @Autowired
    protected TransactionTemplate txTemplate;
    @PersistenceContext
    protected EntityManager em;
    @Autowired
    @Qualifier("applicationTaskExecutor")
    protected ThreadPoolTaskExecutor executor;

    protected SingularAttribute getIdProperty() {
        if (Objects.isNull(ID_PROPERTY_CACHE.get(entityClazz))) {
            synchronized (ID_PROPERTY_CACHE) {
                if (Objects.isNull(ID_PROPERTY_CACHE.get(entityClazz))) {
                    ID_PROPERTY_CACHE.put(entityClazz, findIdProperty());
                }
            }
        }
        return ID_PROPERTY_CACHE.get(entityClazz);
    }

    private SingularAttribute findIdProperty() {
        SingularAttribute idProperty = null;
        Metamodel metamodel = em.getMetamodel();
        EntityType entity = metamodel.entity(entityClazz);
        Set<SingularAttribute> singularAttributes = entity.getSingularAttributes();
        for (SingularAttribute singularAttribute : singularAttributes) {
            if (singularAttribute.isId()) {
                idProperty = singularAttribute;
                break;
            }
        }
        if (Objects.isNull(idProperty)) throw new RuntimeException("id field not found");
        return idProperty;
    }

    private CompletableFuture<Void> executeBatch(List<T> list) {
        return CompletableFuture.runAsync(() -> txTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                list.forEach(em::persist);
            }
        }), executor);
    }

    @Override
    public List<T> findAll(Collection<Serializable> ids) {
        if (ids.size() >= 1000) {
            log.warn("[Find All 方法警告] size={}过大，请尽快修改", ids.size());
        }
        return repository.findAllById(ids);
    }

    @Override
    public List<T> findAll(T entity) {
        return repository.findAll(Example.of(entity, ExampleMatcher.matchingAll()), PageDto.DEFAULT_SORT);
    }

    @Override
    public List<T> findAll(T entity, Sort sort) {
        return repository.findAll(Example.of(entity), sort);
    }

    @Override
    public List<T> findAll(Specification<T> spec) {
        return repository.findAll(spec, PageDto.DEFAULT_SORT);
    }

    @Override
    public List<T> findAll(Specification<T> spec, Sort sort) {
        return repository.findAll(spec, sort);
    }

    @Override
    public List<T> findAll(BiConsumer<Root<T>, CriteriaQuery<T>> queryConsumer) {
        CriteriaQuery<T> criteriaQuery = em.getCriteriaBuilder().createQuery(entityClazz);
        Root<T> root = criteriaQuery.from(entityClazz);
        queryConsumer.accept(root, criteriaQuery);
        return em.createQuery(criteriaQuery).getResultList();
    }

    @Override
    public PageDto<T> page(T entity, PageDto pageDto) {
        return PageDto.of(repository.findAll(Example.of(entity, ExampleMatcher.matchingAll()), pageDto.getPageable()));
    }

    @Override
    public PageDto<T> page(Specification<T> spec, PageDto pageDto) {
        return PageDto.of(repository.findAll(spec, pageDto.getPageable()));
    }

    @Override
    public PageDto<T> page(BiConsumer<Root<T>, CriteriaQuery<?>> queryConsumer, PageDto pageDto) {
        CriteriaBuilder builder = em.getCriteriaBuilder();
//        查询当前分页记录
        CriteriaQuery<T> criteriaQuery = builder.createQuery(entityClazz);
        Root<T> root = criteriaQuery.from(entityClazz);
        criteriaQuery.from(entityClazz);
        queryConsumer.accept(root, criteriaQuery);
        List<T> data = em.createQuery(criteriaQuery)
                .setFirstResult(pageDto.getOffset())
                .setMaxResults(pageDto.getLimit())
                .getResultList();
        pageDto.setRecords(data);
//        查询总记录数
        CriteriaQuery<Long> countQuery = builder.createQuery(Long.class);
        Root<T> roo = countQuery.from(entityClazz);
        queryConsumer.accept(roo, countQuery);
        countQuery.select(builder.count(roo));
        long total = em.createQuery(countQuery).getSingleResult();
        pageDto.setTotal(total);
        return pageDto;
    }

    @Override
    public PageDto<T> slice(T entity, PageDto pageDto) {
        final SimpleSliceJpaRepository simpleSliceJpaRepository = new SimpleSliceJpaRepository(entityClazz, em);
        return PageDto.of(simpleSliceJpaRepository.findAll(Example.of(entity, ExampleMatcher.matchingAll()), pageDto.getPageable()));
    }

    @Override
    public PageDto<T> slice(Specification<T> spec, PageDto pageDto) {
        final SimpleSliceJpaRepository simpleSliceJpaRepository = new SimpleSliceJpaRepository(entityClazz, em);
        return PageDto.of(simpleSliceJpaRepository.findAll(spec, pageDto.getPageable()));
    }

    @Override
    public PageDto<T> slice(BiConsumer<Root<T>, CriteriaQuery<T>> queryConsumer, PageDto pageDto) {
        CriteriaBuilder builder = em.getCriteriaBuilder();
//        查询当前分页记录
        CriteriaQuery<T> criteriaQuery = builder.createQuery(entityClazz);
        Root<T> root = criteriaQuery.from(entityClazz);
        criteriaQuery.from(entityClazz);
        queryConsumer.accept(root, criteriaQuery);
        List<T> data = em.createQuery(criteriaQuery)
                .setFirstResult(pageDto.getOffset())
                .setMaxResults(pageDto.getLimit())
                .getResultList();
        pageDto.setRecords(data);
        return pageDto;
    }

    @Override
    public <ID extends Serializable> Optional<T> findById(ID id) {
        Object actualId = Convert.convert(getIdProperty().getJavaType(), id);
        return repository.findById(actualId);
    }

    @Override
    public Optional<T> findOne(T entity) {
        return repository.findOne(Example.of(entity));
    }

    @Override
    public Optional<T> findOne(Specification<T> spec) {
        return repository.findOne(spec);
    }

    @Override
    public Optional<T> findOne(BiConsumer<Root<T>, CriteriaQuery<T>> queryConsumer) {
        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaQuery<T> criteriaQuery = builder.createQuery(entityClazz);
        Root<T> root = criteriaQuery.from(entityClazz);
        criteriaQuery.from(entityClazz);
        queryConsumer.accept(root, criteriaQuery);
        return Optional.of(em.createQuery(criteriaQuery).getSingleResult());
    }

    @Validate(ValidGroup.Edit.class)
    @Override
    public boolean save(T entity) {
        repository.save(entity);
        return true;
    }

    @Validate(ValidGroup.New.class)
    @Override
    public boolean add(T entity) {
        repository.save(entity);
        return true;
    }

    @Validate(ValidGroup.Edit.class)
    @Override
    public boolean update(T entity) {
        String idProperty = getIdProperty().getName();
        Object actualId = ReflectUtil.getFieldValue(entity, idProperty);
        T old = (T) repository.findById(actualId).get();
        BeanUtil.copyProperties(entity, old, CopyOptions.create().setIgnoreNullValue(true));
        return true;
    }

    @Validate(ValidGroup.New.class)
    @SneakyThrows
    @Override
    public boolean insertInBatch(Collection<T> entities) {
        // 这里涉及到多线程事务，是无法保证多个线程事务的原子性
        // 需要根据业务考虑是否使用该方法
        // 如果需要保证多线程事务的原子性，需要自己实现
        // 参考文章 https://www.cnblogs.com/thisiswhy/p/13948055.html
        txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        CompletableFuture[] futures = Lists.partition(new ArrayList<>(entities), BATCH_SIZE).stream()
                .map(this::executeBatch)
                .toArray(CompletableFuture[]::new);
        CompletableFuture<Void> run = CompletableFuture.allOf(futures);
        run.get();
        return true;
    }

    @Override
    public int update(BiConsumer<Root<T>, CriteriaUpdate<T>> updateConsumer) {
        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaUpdate<T> criteriaUpdate = builder.createCriteriaUpdate(entityClazz);
        Root<T> root = criteriaUpdate.from(entityClazz);
        updateConsumer.accept(root, criteriaUpdate);
        return em.createQuery(criteriaUpdate).executeUpdate();
    }

    @Override
    public int delete(DeleteSpec<T> deleteSpec) {
        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaDelete<T> criteriaDelete = builder.createCriteriaDelete(entityClazz);

        if (ReflectUtil.hasField(entityClazz, DELETED_FIELD)) {
            CriteriaUpdate<T> criteriaUpdate = builder.createCriteriaUpdate(entityClazz);
            Root<T> root = criteriaUpdate.from(entityClazz);
            criteriaUpdate.set(DELETED_FIELD, true);
            Predicate predicate = deleteSpec.toPredicate(root, criteriaDelete, builder);
            criteriaUpdate.where(predicate);
            return em.createQuery(criteriaUpdate).executeUpdate();
        } else {
            Root<T> root = criteriaDelete.from(entityClazz);
            Predicate predicate = deleteSpec.toPredicate(root, criteriaDelete, builder);
            criteriaDelete.where(predicate);
            return em.createQuery(criteriaDelete).executeUpdate();
        }
    }

    @Override
    public <ID extends Serializable> boolean deleteById(ID id) {
        Object actualId = Convert.convert(getIdProperty().getJavaType(), id);
        delete((root, criteriaDelete, criteriaBuilder) -> criteriaBuilder.equal(root.get(getIdProperty().getName()), actualId));
        return true;
    }

    @Override
    public <ID extends Serializable> boolean deleteAllById(Iterable<ID> ids) {
        Type idType = getIdProperty().getJavaType();
        List<Object> list = Streamable.of(ids).map(id -> Convert.convert(idType, id)).toList();
        delete(((root, criteriaDelete, criteriaBuilder) -> root.get(getIdProperty().getName()).in(list)));
        return true;
    }

    @Override
    public long count(T entity) {
        return repository.count(Example.of(entity, ExampleMatcher.matchingAll()));
    }

    @Override
    public long count(Specification<T> spec) {
        return repository.count(spec);
    }

}
